package com.gonsin.gview.logic.utils;

import cn.hutool.core.util.ArrayUtil;
import com.gonsin.gview.annotations.actions.GFilename;
import com.gonsin.gview.annotations.actions.GSession;
import com.gonsin.gview.annotations.data.GParam;
import com.gonsin.gview.logic.exceptions.BaseGViewException;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.lang3.ArrayUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

@Slf4j
public class ValueUtils {

    private static List<String> BASE_TYPE = Arrays.asList(
            "int", "byte", "boolean", "double", "float", "short", "char", "long",
            String.class.getName(),
            Integer.class.getName(),
            Byte.class.getName(),
            Boolean.class.getName(),
            Double.class.getName(),
            Float.class.getName(),
            Short.class.getName(),
            Character.class.getName(),
            Long.class.getName()
    );

    /**
     * 判断是否是基础数据类型的包装类型
     */
    public static boolean isWrapClass(Class clz) {
        try {
            if(BASE_TYPE.contains(clz.getName())){
                return true;
            }
            return ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * TODO 将字符串\map\list 转化成任意类型
     */
    public static <T> T parseValue(Object obj, Type type) {
        if(obj == null){
            return null;
        }
        if(type == Object.class){
            return (T) obj;
        }
        if(obj instanceof Map){
            return (T) mapToObject((Map)obj, (Class<?>) type);
        }
//         && List.class.isAssignableFrom((Class<?>) type)

        if(obj instanceof List){
            List array = new ArrayList();
            List before = (List)obj;
            Class subClass = null;
            if(type instanceof ParameterizedType){
                Type[] subClasses = ((ParameterizedType) type).getActualTypeArguments();
                if(subClasses != null && subClasses.length != 0){
                    subClass = (Class) subClasses[0];
                } else {
                    subClass = Object.class;
                }
            } else {
                subClass = Object.class;
            }

            for(Object item : before){
                array.add(parseValue(item, subClass));
            }
            return (T) array;
        }
        String value = (obj instanceof String) ? (String) obj : obj.toString();
        if(value == null){
            return null;
        }
        if(type.equals(String.class)){
            return (T) value;
        }
        if(value.equals("")){
            return null;
        }
        if(Integer.class == type){
            return (T) Integer.class.cast(Double.valueOf(value).intValue());
        }
        if(Long.class == type){
            return (T) Long.class.cast(Double.valueOf(value).longValue());
        }
        if(String.class == type){
            return (T) value;
        }
        if(Boolean.class == type){
            return (T) (Boolean.valueOf("true".equalsIgnoreCase(value)));
        }
        if(Float.class == type){
            return (T)Float.valueOf(value);
        }
        if(Double.class == type){
            return (T)Double.valueOf(value);
        }
        if(Byte.class == type){
            return (T) Byte.class.cast(Float.valueOf(value).byteValue());
        }
        if(Short.class == type){
            return (T) Short.class.cast(Float.valueOf(value).shortValue());
        }
        if(Character.class == type){
            return (T)Character.valueOf(value.toCharArray()[0]);
        }
        Gson gson = new GsonBuilder()
                .enableComplexMapKeySerialization()
                .disableHtmlEscaping().create();
        Object map = gson.fromJson(value, type);
        return (T) map;
    }



    public static <T> T mapToObject(Map<String, Object> props, Class<?> clazz){
        Object searchPo = null;
        try {
            searchPo = clazz.newInstance();
        } catch (Exception e) {
            log.error(e.toString(), e);
            throw new BaseGViewException(String.format("无法创建类【%s】", clazz.getName()));
        }
        try {
            BeanUtilsBean utils = BeanUtilsBean.getInstance();



            // 非基本类型转换时，使用特殊方法
            Field[] allFields = clazz.getDeclaredFields();
            for(Field field : allFields){
                try {
                    if(!isWrapClass(field.getType()) && !Date.class.isAssignableFrom(field.getType())){
                        if(List.class.isAssignableFrom(field.getType())){
                            Class subClass = null;
                            Type type = field.getGenericType();
                            if(type instanceof ParameterizedType){
                                Type[] subClasses = ((ParameterizedType) type).getActualTypeArguments();
                                if(subClasses != null && subClasses.length != 0){
                                    subClass = (Class) subClasses[0];
                                } else {
                                    subClass = Object.class;
                                }
                            } else {
                                subClass = Object.class;
                            }
                            // TODO 此处可能会有bug，因为getType 会泛化成  List类型，如果一个map里有多个list值，则可能会报错
                            utils.getConvertUtils().register(new CommonListClassConverter(subClass), field.getType());
                        } else if(field.getType().isArray()){

                        } else {
                            utils.getConvertUtils().register(new CommonClassConverter(field.getType()), field.getType());
                        }
                    }
                } catch (Exception ex) {
                    log.warn(ex.toString(), ex);
                    log.warn("设置参数【{}】，设置的内容为“{}“", field.getName(), props.get(field.getName()));
                }
            }

            utils.getConvertUtils().register(new MyDateConverter(), java.util.Date.class);
            utils.getConvertUtils().register(new MyBooleanConverter(), Boolean.class);
            utils.getConvertUtils().register(new MyIntegerConverter(), Integer.class);
            utils.getConvertUtils().register(new MyLongConverter(), Long.class);
            utils.getConvertUtils().register(new MyShortConverter(), Short.class);
            utils.getConvertUtils().register(new MyByteConverter(), Byte.class);
            utils.getConvertUtils().register(new MyDoubleConverter(), Double.class);
            utils.getConvertUtils().register(new MyFloatConverter(), Float.class);
            utils.getConvertUtils().register(new MyCharConverter(), Character.class);



            utils.populate(searchPo, props);
            return (T) searchPo;
        } catch (Exception e) {
            log.error(e.toString() ,e);
            throw new BaseGViewException(String.format("无法创建类【%s】", clazz.getTypeName()));
        }
    }



    /**
     * 方法中是否包含GSession
     */
    public static GSession getSessionAnnotation(Method method, int index) {
        Annotation[] annotations = method.getParameterAnnotations()[index];
        for(int i = 0; i < annotations.length; i++){
            if(annotations[i] instanceof GSession){
                return (GSession) annotations[i];
            }
        }
        return null;
    }

    /**
     * 方法中是否包含 GFilename
     */
    public static GFilename getFilenameAnnotation(Method method, int index) {
        Annotation[] annotations = method.getParameterAnnotations()[index];
        for(int i = 0; i < annotations.length; i++){
            if(annotations[i] instanceof GFilename){
                return (GFilename) annotations[i];
            }
        }
        return null;
    }

    /**
     * 獲取參數名字
     */
    public static String getParamName(Method method, int i) {
        Annotation[] param = method.getParameterAnnotations()[i];
        if(param == null){
            return method.getParameters()[i].getName();
        }
        for(Annotation annotation : param){
            if(annotation instanceof GParam){
                return ((GParam) annotation).value();
            }
        }
        return method.getParameters()[i].getName();
    }


    /**
     * 忽略类型差异，尽最大能力将值设置到变量种
     * @param obj
     * @param field
     * @param value
     */
    public static void lazySetValue(Object obj, Field field, Object value){

        if(obj == null || field == null || value == null){
            return;
        }

        Type origin = value.getClass();
        Type target = field.getGenericType();
        if(TypeUtils.isAssignable(origin, target)){
            setValue(obj, field, value);
            return;
        }

        if(TypeUtils.isAssignable(String.class, target)){
            setValue(obj, field, value.toString());
            return;
        }

        if(TypeUtils.isAssignable(Integer.class, target)){
            String str = value.toString();
            if(str.contains(".")){
                setValue(obj, field, Double.valueOf(value.toString()).intValue());
                return;
            }
            setValue(obj, field, Integer.parseInt(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Long.class, target)){
            String str = value.toString();
            if(str.contains(".")){
                setValue(obj, field, Double.valueOf(value.toString()).longValue());
                return;
            }
            setValue(obj, field, Long.parseLong(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Double.class, target)){
            setValue(obj, field, Double.parseDouble(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Float.class, target)){
            setValue(obj, field, Float.parseFloat(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Boolean.class, target)){
            if("1".equals(value.toString())){
                setValue(obj, field, true);
                return;
            }
            if("true".equals(value.toString())){
                setValue(obj, field, true);
                return;
            }
            setValue(obj, field, false);
            return;
        }



        // 数组
        if(TypeUtils.isArray(target)){
            List list;
            if(!(value instanceof List)){
                list = new ArrayList();
                list.add(value);
            } else {
                list = (List) value;
            }

            Type subType = TypeUtils.getListSubType(list.getClass());
            if (arraySetValue(String.class, subType, list, obj, field)) return;
            if (arraySetValue(Integer.class, subType, list, obj, field)) return;
            if (arraySetValue(Long.class, subType, list, obj, field)) return;
            if (arraySetValue(Double.class, subType, list, obj, field)) return;
            if (arraySetValue(Float.class, subType, list, obj, field)) return;
            if (arraySetValue(Boolean.class, subType, list, obj, field)) return;
            return;
        }

        // 列表
        if(TypeUtils.isAssignable(List.class, target)){
            List list;
            if(!(value instanceof List)){
                list = new ArrayList();
                list.add(value);
            } else {
                list = (List) value;
            }
            Type subType = TypeUtils.getListSubType(list.getClass());
            if (listSetValue(String.class, subType, list, obj, field)) return;
            if (listSetValue(Integer.class, subType, list, obj, field)) return;
            if (listSetValue(Long.class, subType, list, obj, field)) return;
            if (listSetValue(Double.class, subType, list, obj, field)) return;
            if (listSetValue(Float.class, subType, list, obj, field)) return;
            if (listSetValue(Boolean.class, subType, list, obj, field)) return;
            return;
        }

        // MAP
        if(TypeUtils.isAssignable(Map.class, target)){

        }

    }


    /**
     * 设置array的方法，写成一个方法
     * 检测 subClass 和 target是否为同类型
     * 如果是同类型，则直接设置，并且将list转换为数组
     * 最后设置到field里
     */
    private static <T> boolean arraySetValue(Class<T> subClazz, Type target, List list, Object obj, Field field){
        List<AtomicReference<Object>> targetValues = new ArrayList<>();
        if(TypeUtils.isAssignable(subClazz, target)){
            for(int i = 0; i < list.size(); i++){
                AtomicReference<Object> itemTarget = new AtomicReference<>();
                lazySetValue(subClazz, itemTarget, list.get(i));
                targetValues.add(itemTarget);
            }
            T[] out = (T[]) new Object[list.size()];
            for(int i = 0; i < targetValues.size(); i++){
                ArrayUtil.setOrAppend(out, i, targetValues.get(i));
                out[i] = (T) targetValues.get(i).get();
            }
            setValue(obj, field, out);

            return true;
        }
        return false;
    }

    /**
     * 设置array的方法，写成一个方法
     * 检测 subClass 和 target是否为同类型
     * 如果是同类型，则直接设置，并且将list转换为数组
     * 最后设置到field里
     */
    private static <T> boolean listSetValue(Class<T> subClazz, Type targetSubClazz, List list, Object obj, Field field){
        List<AtomicReference<Object>> targetValues = new ArrayList<>();
        if(TypeUtils.isAssignable(subClazz, targetSubClazz)){
            for(int i = 0; i < list.size(); i++){
                AtomicReference<Object> itemTarget = new AtomicReference<>();
                lazySetValue(subClazz, itemTarget, list.get(i));
                targetValues.add(itemTarget);
            }
            List<T> out = new ArrayList<T>();
            for(int i = 0; i < targetValues.size(); i++){
                ArrayUtil.setOrAppend(out, i, targetValues.get(i));
                out.add((T) targetValues.get(i).get());
            }
            setValue(obj, field, out);
            return true;
        }
        return false;
    }


    /**
     * 设置值
     * @param field
     * @param value
     */
    public static void lazySetValue(Class<?> target, AtomicReference<Object> field, Object value){
        Type origin = value.getClass();
//        Type target = field.getGenericType();
        if(TypeUtils.isAssignable(origin, target)){
            field.set(value);
            return;
        }

        if(TypeUtils.isAssignable(String.class, target)){
            field.set(value.toString());
            return;
        }

        if(TypeUtils.isAssignable(Integer.class, target)){
            String str = value.toString();
            if(str.contains(".")){
                field.set(Double.valueOf(value.toString()).intValue());
                return;
            }
            field.set(Integer.parseInt(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Long.class, target)){
            String str = value.toString();
            if(str.contains(".")){
                field.set(Double.valueOf(value.toString()).longValue());
                return;
            }
            field.set(Long.parseLong(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Double.class, target)){
            field.set(Double.parseDouble(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Float.class, target)){
            field.set(Float.parseFloat(value.toString()));
            return;
        }

        if(TypeUtils.isAssignable(Boolean.class, target)){
            if("1".equals(value.toString())){
                field.set(true);
                return;
            }
            if("true".equals(value.toString())){
                field.set(true);
                return;
            }
            field.set(false);
            return;
        }
    }


    /**
     * 利用反射，设置变量
     */
    public static void setValue(Object obj, Field field, Object value){
        field.setAccessible(true);
        try {
            field.set(obj, value);
            return;
        } catch (Exception ex) {

        }
        Class clazz = obj.getClass();
        Method setMethod = getSetMethod(clazz, field);
        if(setMethod == null){
            return;
        }
        setMethod.setAccessible(true);
        try {
            setMethod.invoke(obj, value);
        } catch (Exception ex) {

        }
    }

    private static Method getSetMethod(Class clazz, Field batchField) {
        Method result = null;
        String propertyName = batchField.getName();
        Method[] methods = clazz.getDeclaredMethods();
        if(ArrayUtils.isEmpty(methods)){
            return null;
        }

        if (methods != null) {
            int i = 0;

            for(int icount = methods.length; i < icount; ++i) {
                Method m = methods[i];
                String methodName = m.getName();
                String targetMethodName = "set" + propertyName.substring(0, 1).toUpperCase(Locale.ROOT) +
                        propertyName.substring(1);
                if(methodName.equals(targetMethodName)){
                    return m;
                }
            }
        }
        return null;
    }


    /**
     * 在一个类中所有的Field，查找特定批注
     * @param clazz
     * @param anno
     * @return
     */
    public static Field findAnnotationField(Class clazz, Class<? extends Annotation> anno) {
        Field[] allField = clazz.getDeclaredFields();
        if(ArrayUtils.isEmpty(allField)){
            return null;
        }
        for(Field f : allField){
            Annotation annotation = f.getAnnotation(anno);
            if(annotation != null){
                return f;
            }
        }
        return null;
    }
}
